app.units = {
	init : function() {
		app.player = app.UnitFactory.createPlayer('warrior', 'Test player');
	}
};

/**
 * Base unit object
 */
app.unit = {
	// Level
	level : 1,
	// Name
	name : 'Unit',
	// Attributes list
	attributes : [],
	items : [],
	
	/**
	 * Initialize unit
	 */
	init : function() {},
	
	/**
	 * Get attribute value
	 *
	 * @param name Attribute name
	 * @return Attribute value
	 */
	getAttr : function(name) {
		var value = 0;
		
		switch (name) {
			case 'speed' :
				value = this.getAttr('agility');
				break;
			case 'accuracy' :
				value = (this.getAttr('agility') > 50 ? 50 : this.getAttr('agility')) + 50;
				break;
			default :
				value = this.attributes[name] ? this.attributes[name].value : 0;
		}
		
		value = parseInt(value);
		
		return value;
	},
	
	/**
	 * Get attribute maximum value
	 *
	 * @param name Attribute name
	 * @return Attribute maximum value
	 */
	getAttrMax : function(name) {
		var value = 0;
		
		switch (name) {
			case 'speed' :
				value = this.getAttrMax('agility');
				break;
			case 'accuracy' :
				value = (this.getAttrMax('agility') > 50 ? 50 : this.getAttrMax('agility')) + 50;
				break;
			default :
				value = this.attributes[name] ? this.attributes[name].maxValue : 0;
		}
		
		value = parseInt(value);
		
		return value;
	},
	
	/**
	 * Get unit damage
	 * Unit damage is currently modified by strength, but it will be changed in future
	 *
	 * @return Damage value
	 */
	getDamage : function() {
		var value = 0, min = 0, max = 0, mod = 0;
		
		min = this.getAttr('damage');
		max = this.getAttrMax('damage');
		mod = this.getAttr('strength');
		
		if (this.items && this.items.length > 0) {
			for (var i = 0; i < this.items.length; i++) {
				var item = this.items[i];
				
				if (item.attributes['damage'] && item.attributes['damage'].value) {
					min += item.attributes['damage'].value;
					max += item.attributes['damage'].maxValue;
				}
				
				if (item.attributes['strength'] && item.attributes['strength'].value) {
					mod += item.attributes['strength'].value;
				}
			}
		}
		
		value = min;
		
		if (min != max) {
			value += parseInt(Math.random() * (max - min));
		}
		
		value += value * mod * 2 / 100;
		value = Math.round(value);
		
		return value;
	},
	
	/**
	 * Get unit defence
	 * Defence is modified by strength, but will be changed in future
	 *
	 * @return Defence value
	 */
	getDefence : function() {
		var value = 0, mod = 0;
		
		value = this.getAttr('defence');
		mod = this.getAttr('strength');
		
		if (this.items && this.items.length > 0) {
			for (var i = 0; i < this.items.length; i++) {
				var item = this.items[i];
				
				if (item.attributes['defence'] && item.attributes['defence'].value) {
					value += item.attributes['defence'].value;
				}
				
				if (item.attributes['strength'] && item.attributes['strength'].value) {
					mod += item.attributes['strength'].value;
				}
			}
		}
		
		value += value * mod * 2 / 100;
		value = Math.round(value);
		
		return value;
	},
	
	/**
	 * Get attribute value in percent
	 *
	 * @param name Attribute name
	 * @param percentSign Add a % sign to value; "true" by default
	 * @return Attribute value in percent
	 */
	getAttrPercent : function(name, percentSign) {
		percentSign = typeof percentSign == 'undefined' ? true : percentSign;
		var value = (this.getAttr(name) / this.getAttrMax(name) * 100).toFixed(2).replace('.00', '');
		
		if (percentSign) {
			value += '%';
		}
		
		return value;
	},
	
	/**
	 * Get formatted attribute value; format is "<value>/<maxValue> (<percentage>)"
	 *
	 * @param name Attribute name
	 * @param includePercent Include a percentage section; "true" by default
	 * @return Attribute string
	 */
	getAttrString : function(name, includePercent) {
		includePercent = typeof includePercent == 'undefined' ? true : includePercent;
		var value = this.getAttr(name) + '/' + this.getAttrMax(name);
		
		if (includePercent) {
			value += ' (' + this.getAttrPercent(name) + ')';
		}
		
		return value;
	},
	
	getAttrWithMods : function(name) {
		var minValue = 0, maxValue = 0, mods, mult = 0, origMinValue = 0, origMaxValue = 0;
		
		minValue = this.getAttr(name);
		maxValue = this.getAttrMax(name);
		mods = this.getAttrMods(name);
		mult = this.getAttrMult(name);
		
		if (mods) {
			origMinValue = minValue;
			origMaxValue = maxValue;
			
			minValue += (typeof mods != 'object') ? mods : mods.min;
			maxValue += (typeof mods != 'object') ? mods : mods.max;
		}
		
		if (mult) {
			minValue += Math.round(minValue * mult / 100);
			maxValue += Math.round(maxValue * mult / 100);
		}
		
		return {
			min : minValue,
			max : maxValue,
			origMin : origMinValue,
			origMax : origMaxValue
		};
	},
	
	/**
	 * Get the DOM node, containing attribute value with modifiers
	 * Returns single value if min equals max or returns both values separated with dash
	 * If attribute has modifiers, then modified value(s) will be displayed before original value(s) with 'buff' or 'defuff' class depending on modifier value
	 *
	 * @param name Attribute name
	 * @return Object
	 */
	getAttrWithModsObject : function(name) {
		var attrWithMods, container, value, valueWithMods;
		
		attrWithMods = this.getAttrWithMods(name);
		container = $('<div>');
		value = $('<span>').addClass('value').appendTo(container);
		
		value.text((attrWithMods.origMin == attrWithMods.origMax) ? attrWithMods.origMin : (attrWithMods.origMin + ' - ' + attrWithMods.origMax));
		
		if ((attrWithMods.min != attrWithMods.origMin) || (attrWithMods.max != attrWithMods.origMax)) {
			valueWithMods = $('<span>').addClass('bigger').text((attrWithMods.min == attrWithMods.max) ? attrWithMods.min : (attrWithMods.min + ' - ' + attrWithMods.max)).prependTo(container);
			value.text(' (' + value.text() + ')').addClass('smaller');
			
			if (attrWithMods.min > 0 && attrWithMods.max > 0) {
				valueWithMods.addClass('buff');
			}
			else {
				valueWithMods.addClass('debuff');
			}
		}
		
		return container;
	},
	
	/**
	 * Get attribute modifier
	 *
	 * @param name Attribute name
	 * @return A single value or an object with "min" and "max" properties
	 */
	getAttrMods : function(name) {
		var minValue = 0, maxValue = 0;
		
		if (this.items) {
			for (var i = 0; i < this.items.length; i++) {
				if (this.items[i].attributes && this.items[i].attributes[name]) {
					if (this.items[i].attributes[name].value) {
						minValue += this.items[i].attributes[name].value;
					}
					
					if (this.items[i].attributes[name].maxValue) {
						maxValue += this.items[i].attributes[name].maxValue;
					}
				}
			}
		}
		
		return {
			'min' : minValue,
			'max' : maxValue
		};
	},
	
	/**
	 * Get attribute multiplier value
	 *
	 * @param name Attribute name
	 * @return Multiplier value
	 */
	getAttrMult : function(name) {
		var mult;
		
		if (!this.attributes[name].multiplier) {
			return false;
		}
		
		mult = this.attributes[name].multiplier.call(this);
		
		return mult;
	},
	
	/**
	 * Set the attribute value
	 * Triggers the "unitattributechange" event
	 *
	 * @param name Attribute name
	 * @param value Value
	 */
	setAttr : function(name, value) {
		if (!this.attributes[name]) {
			return;
		}
		
		this.attributes[name].value = value > 0 ? value : 0;
		
		if (name == 'exp') {
			if (this.getAttr(name) >= this.getAttrMax(name)) {
				this.levelUp();
			}
		}
		
		$(this).trigger('unitattributechange', name);
	},
	
	/**
	 * Modify attribute value
	 *
	 * @param name Attribute name
	 * @param value Value to modify attribute with
	 */
	modifyAttr : function(name, value) {
		var value = this.getAttr(name) + (+value);
		
		this.setAttr(name, value);
	},
	
	/**
	 * Adds some exp to unit
	 *
	 * @param value Exp amount
	 */
	addExp : function(value) {
		this.modifyAttr('exp', value);
	},
	
	/**
	 * Check if unit is dead
	 *
	 * @return "true" if unit is dead
	 */
	isDead : function() {
		return this.getAttr('health') <= 0;
	},
	
	/**
	 * Process unit levelup
	 * Triggers the "unitlevelup" event
	 */
	levelUp : function() {
		this.level++;
		
		for (var attr in this.attributes) {
			var attribute = this.attributes[attr];
			var newValue = (typeof attribute.ratio == 'function') ? attribute.ratio.call(attribute.maxValue) : (attribute.maxValue + attribute.ratio);
			var oldValue = this.getAttrMax('exp');

			this.attributes[attr].maxValue = newValue;
			attr != 'exp' ? this.setAttr(attr, newValue) : this.modifyAttr('exp', -oldValue);
		}
		
		$(this).trigger('unitlevelup');
	}
};

/**
 * Factory for creating units and unit types
 */
app.UnitFactory = {
	// Unit types
	_types : [],
	
	/**
	 * Load unit types
	 *
	 * @param types Associative array of unit types
	 */
	load : function(types) {
		$.each(types, function(id, type) {
			var base = type.base;
			
			if (type.items) {
				for (var i = 0; i < type.items.length; i++) {
					type.items[i] = app.ItemsMgr.create(type.items[i]);
				}
			}
			
			if (base) {
				delete type.base;
				
				app.UnitFactory.extendType(id, base, type);
			}
			else {
				app.UnitFactory.createType(id, type);
			}
		});
	},
	
	/**
	 * Create new unit type
	 *
	 * @param id Type ID
	 * @param options New unit type options
	 * @return Unit type
	 */
	createType : function(id, options) {
		return this.extendType(id, app.unit, options);
	},
	
	/**
	 * Create new unit type, using another type as basis
	 *
	 * @param id Type ID
	 * @param baseType Base unit type; can be type ID or type itself
	 * @param options New unit type options
	 * @return Unit type
	 */
	extendType : function(id, baseType, options) {
		options = options || {};
		options.id = id;
		
		if (options.attributes) {
			for (var attr in options.attributes) {
				if (!options.attributes[attr].maxValue) {
					options.attributes[attr].maxValue = options.attributes[attr].value;
				}
			}
		}
		
		if (typeof baseType != 'object') {
			baseType = this.getType(baseType);
		}
		
		return this._types[id] = $.extend(true, {}, baseType, options);
	},
	
	/**
	 * Get the unit type from cache
	 *
	 * @param type Type ID
	 * @return Unit type
	 */
	getType : function(type) {
		return this._types[type];
	},
	
	/**
	 * Create new unit of given type
	 *
	 * @param type Type of unit
	 * @return Unit
	 */
	create : function(type) {
		var unit = $.extend(true, {}, this._types[type]);
		
		unit.init && unit.init();
		
		return unit;
	},
	
	/**
	 * Create a player unit
	 *
	 * @param playerClass Class
	 * @param name Player name
	 * @param options Associative array of options
	 * @return player unit
	 */
	createPlayer : function(playerClass, name, options) {
		var player;
		
		playerClass = playerClass || 'warrior';
		player = this.create('class.' + playerClass);
		player.name = name;
		player.className = playerClass;
		
		return player;
	}
};

/**
 * Data
 */
(function() {
	var types = {
		'base' : {
			attributes : {
				exp : {value : 0, maxValue : 5, ratio : function() {
					return this * 2;
				}},
				health : {value : 5, ratio : 0},
				strength : {value : 1, ratio : 0},
				agility : {value : 1, ratio : 0},
				damage : {value : 1, ratio : .2, multiplier : function() {
					return this.getAttr('strength') * 2;
				}},
				defence : {value : 0, ratio : .2, multiplier : function() {
					return this.getAttr('strength') * 2;
				}}
			}
		},
		
		'player' : {
			name : 'Player',
			base : 'base',
			attributes : {
				health : {value : 20, ratio : 5},
				strength : {value : 1, ratio : 1},
				agility : {value : 1, ratio : 1},
				sight : {value : 1, ratio : .1},
				damage : {ratio : .25}
			},
			items : [],
			
			init : function() {
				this._mainLevel = $('#playerInfo .level');
				this._mainName = $('#playerInfo .name .value');
				this._mainLevelUp = $('#playerInfo .levelup');
				this._mainHealthBar = $('#playerInfo .health');
				this._mainExpBar = $('#playerInfo .exp');

				$('#playerStats').click(function() {
					app.character.showStats();
					
					return false;
				});
				
				this._mainLevel.text(this.level);
				this._mainName.text(this.name);
				this._mainHealthBar.attr('title', this.getAttrString('health')).find('div').width(this.getAttrPercent('health'));
				this._mainExpBar.attr('title', this.getAttrString('exp')).find('div').width(this.getAttrPercent('exp'));
				
				$(this).bind({
					'unitattributechange' : function(event, name) {
						if (name == 'health') {
							this._mainHealthBar.attr('title', this.getAttrString(name)).find('div').width(this.getAttrPercent(name));
						}
						else if (name == 'exp') {
							this._mainExpBar.attr('title', this.getAttrString(name)).find('div').width(this.getAttrPercent(name));
						}
					},
					'unitlevelup' : function(event) {
						this._mainLevel.text(this.level);
						this._mainLevelUp.show().delay(500).fadeOut('slow');
					}
				});
			}
		},
		
		/**
		 * =====================================
		 * =============== CLASSES ===============
		 * =====================================
		 */
		'class.warrior' : {
			name : 'Warrior',
			base : 'player',
			attributes : {
				health : {value : 25},
				strength : {value : 2, ratio : 1.25},
				agility : {ratio : .5},
				damage :{ratio : 1/3},
				defence : {value : 1, ratio : .2}
			},
			items : ['sword']
		},
		'class.archer' : {
			name : 'Archer',
			base : 'player',
			attributes : {
				strength : {value : 1, ratio : .5},
				agility : {value : 2, ratio : 1.25},
				damage : {ratio : .3},
				defence : {value : 0, ratio : .15}
			},
			items : ['bow']
		},
		
		/**
		 * Base mob type
		 */
		'mob' : {
			name : 'Mob',
			base : 'base',
			attributes : {
				health : {ratio : 2},
				strength : {ratio : .5},
				agility : {ratio : .5},
				aggression : {value : 50, ratio : 2},
				damage : {value : 1, maxValue : 2}
			}
		},
		
		/**
		 * =====================================
		 * ================ MOBS ================
		 * =====================================
		 */
		'rat' : {
			name : 'Rat',
			base : 'mob'
		},
		
		'big-rat' : {
			name : 'Big rat',
			base : 'rat',
			attributes : {
				health : {value : 10},
				aggression : {value : 60},
				damage : {value : 1, maxValue : 3}
			}
		}
	};
	
	app.UnitFactory.load(types);
})();